Conversation
Pre-allocates a batch of StringView objects tied to a single backing
string. pool.view(offset, length) returns a pre-built view with just
two long writes — zero Ruby object allocation in steady state.
Designed for the looped parser pattern:
pool = StringView::Pool.new(buffer)
records.each do |record|
key = pool.view(key_offset, key_len)
value = pool.view(val_offset, val_len)
process(key, value)
pool.reset! # rewind cursor, reuse views next iteration
end
Performance (YJIT, Apple M-series):
StringView.new: ~235ns/view (4.3M views/s)
Pool.view (reuse): ~13ns/view (77M views/s) — 18x faster
GC-safe: every view is a real Ruby object managed by the GC. The pool
holds them in a Ruby Array. Each view holds a strong reference to the
backing string. Compaction-safe via rb_gc_mark_movable + dcompact.
Pool API:
Pool.new(string) — create pool, pre-allocate 32 views
pool.view(byte_off, byte_len) — return next pre-allocated view
pool.reset! — rewind cursor (views get reused)
pool.size — views handed out since last reset
pool.capacity — total pre-allocated slots
pool.backing — the frozen backing string
Growth: starts at 32 slots, doubles when exhausted (32→64→128→...).
After a few iterations the pool stabilizes at the high-water mark.
285 tests, 643 assertions, 0 failures.
Add ~65 new tests covering: - Construction edge cases: empty string, binary, large, frozen - View boundary cases: start/end, adjacent, overlapping, same range - Thorough bounds checking: negative offset/length, overflow - Exponential growth: detailed sequence, many doublings, validity - Reset lifecycle: multiple resets, reset-then-grow, slot reuse, materialize-before-reset pattern - Multibyte/UTF-8: CJK, emoji, mixed-width characters - Full StringView API interop: include?, start_with?, getbyte, to_i, to_f, comparison, hash keys, each_byte, slicing, delegates - Stress: 10K views without reset, rapid reset cycling, alternating iteration sizes - GC safety: during use, compaction, multiple pools same backing - Real-world parser patterns: CSV, HTTP headers, log lines, fixed-width records 350 tests, 10978 assertions, 0 failures.
paracycle
approved these changes
Mar 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
StringView::Poolpre-allocates a batch of StringView objects tied to a single backing string.pool.view(offset, length)returns a pre-built view with just two long writes — zero Ruby object allocation in steady state.Why
When parsing a large buffer, you extract dozens of substrings per parse call. Each
StringView.newallocates one Ruby object (~235ns). In a hot loop processing thousands of messages, this dominates:With a pool, each
.view()is ~13ns (18x faster) and allocates nothing.The looped parser pattern
After the first iteration, the pool has enough capacity and every subsequent
.view()call is zero-allocation. The pool stabilizes at the high-water mark of views needed per iteration.Performance
GC safety
Every view in the pool is a real Ruby object managed by the GC. The pool holds them in a Ruby Array (GC-visible). Each view holds a strong reference to the backing string via
rb_gc_mark_movable. Compaction-safe viadcompactcallbacks. No tricks, no unsafe pointers, no finalizers.API
Growth: starts at 32 slots, doubles when exhausted (32 → 64 → 128 → …).
View lifetime after reset!
After
pool.reset!, previously returned views are still valid Ruby objects, but their offset/length will be overwritten by the next.view()call that reuses that slot. If you need a view to outlive a reset:.to_sto materialize it into a String before resettingStringView.newfor long-lived viewsreset!(let the pool grow — views stay valid forever)Tests
285 tests, 643 assertions, 0 failures. Includes:
.view()when pre-warmed vs 1 perStringView.new